Skip to content

feat: add a useResponseClassTransformer global option #329

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jul 21, 2020

Conversation

epiphone
Copy link

Hi, first of all thanks for a great library!

By default response values are coerced to plain objects with class-transformer. When returning large objects (#226) or values with complex serialization logic (e.g. Mongoose documents #149) you might opt for the default toJSON handler instead.

This PR adds a global useResponseClassTransformer option parameter for toggling response transformation. It defaults to true, and is overridden by classTransformer: false.

Also related to #179.

@@ -129,14 +135,14 @@ export abstract class BaseDriver {

protected transformResult(result: any, action: ActionMetadata, options: Action): any {
// check if we need to transform result
const shouldTransform = (this.useClassTransformer && result != null) // transform only if enabled and value exist
const shouldTransform = (this.useClassTransformer && this.useResponseClassTransformer && result != null) // transform only if enabled and value exist
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not updated comment

@MichalLytek
Copy link
Contributor

Isn't that the feature that @NoNameProvided promised to implement a few months ago? 😉

@NoNameProvided
Copy link
Member

Yes, it was. Shame I haven't had the time. I haven't reviewed this either because I think we had agreed on a slightly different approach, but I am not sure, and I don't have time to read it back now. I have a lot of thing on my table now, so I struggle to find any time to spend on my hobbies (including this lib) right now.

@epiphone
Copy link
Author

epiphone commented Dec 4, 2017

Hi, I updated the PR to better match with @NoNameProvided's solution outlined here, i.e. added transformRequest and transformResponse options to controller and route decorators. Gotta agree this approach seems more future-proof 😄

What do you think?

Copy link
Contributor

@MichalLytek MichalLytek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please refactor your test entities to make sure that class-transformer option works - use @Exclude and @Expose decorator on class properties instead of toJSON.

/**
* Extra options that apply to each controller action.
*/
export interface ControllerOptions extends TransformationOptions {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that you should implement TransformationOptions, not extending it - it would be ready for future extension/changes because you can't extend multiple interfaces.

assertRequest([3001, 3002], "post", "default", { firstName: "Umed", lastName: "Khudoiberdiev" }, response => {
expect(initializedUser).to.be.instanceOf(User);
expect(response).to.have.status(200);
expect(response.body.lastName).to.be.defined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've set transformResponse: false but you expect lastName to be defined even if lastName is excluded when class-transformer is disabled... I don't get it 😕

}

@JsonController("/transform", {transformRequest: true, transformResponse: true})
class NoResponseTransformController {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NoResponseTransformController? No? 😆

assertRequest([3001, 3002], "post", "default", { firstName: "Umed", lastName: "Khudoiberdiev" }, response => {
expect(initializedUser).to.be.instanceOf(User);
expect(response).to.have.status(200);
expect(response.body.lastName).to.be.defined;
Copy link
Contributor

@MichalLytek MichalLytek Dec 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • default: ...lastName).to.be.defined;
  • enable: ...lastName).not.to.be.undefined;
    Please be consistent with assertion, don't negate the negative expectation 😉

assertRequest([3001, 3002], "post", "override", { firstName: "Umed", lastName: "Khudoiberdiev" }, response => {
expect(initializedUser).not.to.be.instanceOf(User);
expect(response).to.have.status(200);
expect(response.body.lastName).to.be.defined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't you use .exist here? I can't see defined in chai documentation 😕

@epiphone
Copy link
Author

epiphone commented Dec 5, 2017

Thanks for the feedback @19majkel94, lots of good points in there 👍 . I refactored the tests, let me know if there's anything else you'd like to see changed.

I did have some trouble with class-transformer's global metadata storage: calling defaultMetadataStorage.clear() (here for example) deletes the class-transformer metadata of all defined classes, so I had to move the User class definitions inside a before-block and assign it to a variable in the outer scope. The end result is still reasonably clean, I think, but let me know if you'd have a better solution.

@MichalLytek
Copy link
Contributor

MichalLytek commented Dec 5, 2017

Yeah, it clears because you call clear 😆 But I don't see the need to do change this - pleerock's configuration worked well, all metadata was keeped, only after all tests the storage was cleared. Why you had to change it?

@epiphone
Copy link
Author

epiphone commented Dec 6, 2017

Because the way it was set up, after a test module finished it would not only clear the metadata of its own test classes, but also of all the classes declared in the following tests. For example after class-transformer-options.ts finished, it wiped out User's metadata in global-options.spec.ts which would break the test. That's why I ended up delaying the class declarations until the before block; I'd be happy to refactor it though if you have an alternative solution in mind.

@MichalLytek
Copy link
Contributor

So the problem was that pleerock uses the class-transformer only in one test so clearing metadata wasn't affect other tests but now you have tests also in other files so it clears the metadata of all tests, right? 😄

@epiphone
Copy link
Author

epiphone commented Dec 7, 2017

Exactly 👍 . I took a peek at class-transformer tests, seems like they're handling things in a similar way, declaring classes in the upper describe and it blocks.

@MichalLytek
Copy link
Contributor

Ok, you have user.lastName || "default"; in your tests but I can't see a test case that is using it. Please make a one more test case that have transformRequest: true and transformResponse: false to see that it's working and we are ready to merge 😉

@epiphone
Copy link
Author

epiphone commented Dec 26, 2017

Hey, sorry for the delay, got kinda busy over the holidays! I added the test case and removed user.lastName = "default"; where it's unused.

Copy link
Contributor

@MichalLytek MichalLytek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@MichalLytek
Copy link
Contributor

I'm not sure if @pleerock will like the options object inside decorator so I will wait for his review before merging this PR 😉

@vekexasia
Copy link

Hello, i'm jumping here to understand if this was holded back due to an alternative way to use both JSON controllers and "Raw" controllers that returns buffers

@epiphone
Copy link
Author

I'm also curious about what's holding this back. Care to comment @NoNameProvided ?

@f4z3k4s
Copy link

f4z3k4s commented Jan 17, 2019

@pleerock, @NoNameProvided could you please have a look at this? This is still an existing issue :(

@kunokdev
Copy link

Is there anything happening regarding this?

@CSLTech
Copy link

CSLTech commented Jun 29, 2019

Is there still something holding this back @NoNameProvided @pleerock ?

@github-actions
Copy link

Stale pull request message

@jotamorais jotamorais added this to the 0.8.x release milestone Feb 12, 2020
@MohammedSiddiqui
Copy link

What's the update on this ? 😅

@hallsamuel90
Copy link

Also curious about the status of this?

@jotamorais
Copy link
Member

@hallsamuel90, @MohammedSiddiqui @epiphone, @CSLTech, @kunokdev, @f4z3k4s, @vekexasia, @MichalLytek.

I'm planning to merge this PR and release this tomorrow night among some other fixes:

@yurkagon
Copy link

yurkagon commented Jul 7, 2020

Still waiting :)

@jotamorais jotamorais merged commit cd26ae1 into typestack:master Jul 21, 2020
@NoNameProvided NoNameProvided changed the title Add a useResponseClassTransformer global option feat: add a useResponseClassTransformer global option Aug 9, 2020
@typestack typestack locked as resolved and limited conversation to collaborators Aug 9, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

Successfully merging this pull request may close these issues.